import socket
from txosc import osc

from osc_sender import UnicastOSCSender, MulticastOSCSender
from osc_receiver import UnicastOSCReceiver, MulticastOSCReceiver

from ..BaseIO import BaseIO
from config import Config
from osc_base import OSCNode

def join_address(*args):
    s = '/'.join(['/'.join(arg.split('/')) for arg in args])
    return s

class oscIO(BaseIO, Config):
    io_classes = {'Unicast':(UnicastOSCReceiver, UnicastOSCSender), 
                  'Multicast':(MulticastOSCReceiver, MulticastOSCSender)}
    _confsection = 'OSC'
    def __init__(self, **kwargs):
        BaseIO.__init__(self, **kwargs)
        Config.__init__(self, **kwargs)
        self.register_signal('client_added', 'unique_address_changed')
        self.default_root_address = kwargs.get('root_address', socket.gethostname())
        self.root_address = self.default_root_address
        self.app_address = kwargs.get('app_address', 'OSCApp')
        self.osc_tree = OSCNode(root_node=True, transmit_callback=self.on_node_tree_send)
        self.hostdata = {}
        self.hostdata_info = {'mcastaddr':{'conf':'multicast_address', 'default':'224.168.2.9'}, 
                              'mcastport':{'conf':'multicast_port', 'default':18889}, 
                              'hostaddr':{'conf':'host_address', 'default':'localhost'}, 
                              'hostport':{'conf':'host_port', 'default':18888}}
        for k, v in self.hostdata_info.iteritems():
            val = kwargs.get(k, self.get_conf(v['conf'], v['default']))
            d = {k:type(v['default'])(val)}
            d.update({'update_conf':k in kwargs})
            self.set_hostdata(**d)
            
        self._iotype = None
        self._sender = None
        self._receiver = None
        self.init_clients()
        s = self.get_conf('use_unique_addresses', 'True')
        flag = not s == 'False'
        self.set_use_unique_address(flag, update_conf=False)
        io = self.get_conf('connection_type', 'Multicast')
        self.build_io(iotype=io, update_conf=False)
        
    @property
    def iotype(self):
        return self._iotype
    @iotype.setter
    def iotype(self, value):
        if value != self._iotype:
            self._iotype = value
            self.set_address_vars()
            
    def set_use_unique_address(self, flag, update_conf=True):
        self.use_unique_address = flag
        if update_conf:
            self.update_conf(use_unique_addresses=flag)
        self.set_address_vars()
        self.emit('unique_address_changed', state=flag)
    
    def set_address_vars(self):
        if self.iotype == 'Multicast' or self.use_unique_address:
            self.root_address = self.default_root_address
            self.add_client_name(None, update_conf=False)
        else:
            self.root_address = self.app_address
            self.root_node.setName(self.root_address)
    
    def set_hostdata(self, **kwargs):
        for key, val in kwargs.iteritems():
            if key in self.hostdata_info:
                setattr(self, key, val)
                self.hostdata.update({key:val})
        update = kwargs.get('update_conf')
        if update is not None:
            del kwargs['update_conf']
            update = False
        if update:
            #self.update_conf(**kwargs)
            self.update_hostdata()
            
    def update_hostdata(self):
        for key in self.hostdata_info.iterkeys():
            self.hostdata.update({key:getattr(self, key)})
        d = {}
        for key, val in self.hostdata.iteritems():
            d.update({self.hostdata_info[key]['conf']:val})
        self.update_conf(**d)
    
    def init_clients(self):
        s = 'null'
        self.root_node = self.osc_tree.add_new_node(name=s)
        names = self.get_conf('client_names', [s])
        if type(names) == str:
            names = [names]
        self.client_names = set(names)
        self.add_client_name(None, update_conf=False)
        addresses = self.get_conf('client_addresses', [])
        if type(addresses) == str:
            addresses = [addresses]
        self.client_addresses = set(addresses)
        self.add_client_address(None, update_conf=False)
    
    def build_io(self, **kwargs):
        iotype = kwargs.get('iotype')
        if iotype is None:
            iotype = self.iotype
        update_conf = kwargs.get('update_conf', True)
        self.iotype = iotype
        iokwargs = {'root_address':self.root_address, 'osc_tree':self.osc_tree}
        iokwargs.update(self.hostdata)
        
        if self.connected:
            self.do_disconnect()
        
        self._receiver = self.io_classes[iotype][0](**iokwargs)
        self._sender = self.io_classes[iotype][1](**iokwargs)
        if update_conf:
            self.update_conf(connection_type=iotype)
        
    def add_client_name(self, name, update_conf=True):
        s = self.build_curly_wildcard(self.client_names)
        self.osc_tree._wildcardNodes.discard(s)
        if name is not None:
            self.client_names.add(name)
        if len(self.client_names) > 1 and 'null' in self.client_names:
            self.client_names.discard('null')
        s = self.build_curly_wildcard(self.client_names)
        self.root_node.setName(s)
        self.osc_tree._wildcardNodes.add(s)
        if update_conf:
            self.update_conf(client_names=list(self.client_names))
        self.emit('client_added', name=name)
    
    def add_client_address(self, address, update_conf=True):
        if address is not None:
            self.client_addresses.add(address)
        if update_conf:
            self.update_conf(client_addresses=list(self.client_addresses))
        self.emit('client_added', name=address)
        
    def build_curly_wildcard(self, l):
        return '{%s}' % (','.join(list(l)))
        
    def do_connect(self, **kwargs):
        self.update_hostdata()
        new_kwargs = kwargs.copy()
        new_kwargs.update(self.hostdata)
        if self._receiver is None and self._sender is None:
            self.build_io()
        for obj in [self._receiver, self._sender]:
            obj.do_connect(**kwargs)
        self.connected = True
        
    def do_disconnect(self, **kwargs):
        for obj in [self._receiver, self._sender]:
            obj.do_disconnect(**kwargs)
        self.connected = False
        
    def on_node_tree_send(self, **kwargs):
        address = kwargs.get('address')
        address[0] = self.root_address
        path = '/' + join_address(*address)
        args = self.pack_args(kwargs.get('value'))
        msg = osc.Message(path, *args)
        if self._sender is not None:
            if self.iotype == 'Multicast':
                self._sender._send(msg)
            elif self.iotype == 'Unicast':
                for addr in self.client_addresses:
                    self._sender._send(msg, addr)
        
    def pack_args(self, value):
        if isinstance(value, list) or isinstance(value, tuple):
            return value
        elif value is None:
            return []
        return [value]


